-- Radical by Basil Voronkov
-- Module 'xml'
---------------------------------------------
-- This module contains 'ad hoc' XML parsing logic.
-- Only very basic XML document can be processed correctly,
-- entities, preprocessor, and many other things are not
-- supported. Parsing is done using regex.
xml = {}

function xml.parseargs(s)
  local arg = {};
  string.gsub(s, "(%w+)=([\"'])(.-)%2", 
    function (w, _, a) arg[w] = a end)
  return arg;
end
    
function xml.collect(fn,s)
  local stack = {};
  local top = {};
  table.insert(stack, top);
  local ni,c,label,xarg,empty;
  local i, j = 1, 1;  
  
  while true do
    local ni,j,c,label,xarg,empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i);    
    if not ni then 
      break;
    end    
    local text = string.sub(s, i, ni-1);
    if not string.find(text, "^%s*$") then
      table.insert(top, text);
    end    
    if empty == "/" then
      table.insert(top, {label=label, xarg=xml.parseargs(xarg), empty=1});
    elseif c == "" then
      top = {label=label, xarg=xml.parseargs(xarg)};
      table.insert(stack, top);
    else
      local toclose = table.remove(stack);  
      top = stack[#stack];
      if #stack < 1 then
        error(fn..': nothing to close with '..label);
      end      
      if toclose.label ~= label then
        error(fn..' trying to close '..toclose.label..' with '..label);
      end      
      table.insert(top, toclose);
    end
    i = j+1;
  end
  
  local text = s:sub(i);  
  if not string.find(text, "^%s*$") then
    table.insert(stack[#stack], text);
  end  
  if #stack > 1 then
    error("unclosed "..stack[#stack].label);
  end
  
  return stack[1];
end

function xml.read(f)
  local file = assert(io.open(f, "r"));
  local src = file:read("*all");  
  file:close();
  return xml.collect(f, src);
end